Implementing Single Transactions

As previously mentioned, the simplest GPIF waveforms to produce are the single read and single write waveforms. Performing this function first not only enhances the initial user experience with the design, but also allows the design to be in fairly good shape, fairly quickly. Of course, for high bandwidth applications the natural migration is then to create FIFO read and write transactions.  But, the implementation of single transactions is the right place to start. The first section discusses the single transaction waveforms implemented using GPIF Designer and the next section covers the firmware that triggers them.

 
Figure 12. GPIF Designer Block Diagram

The GPIF Designer makes creating GPIF waveform descriptors easy. Rather than having to know each bit of the waveform descriptor opcode bytes in detail to create a waveform, the GPIF Designer allows you to “draw” each waveform and export the waveform descriptors to a self-contained file, typically called gpif.c. When you open GPIF Designer, it will present you with a block diagram view of the physical interconnect as shown in Figure 12. You can then use the block diagram view to name the individual CTLx lines and RDYn signals. These names propagate into the waveform tabs, allowing you to personalize each waveform and determine which signals are being manipulated. You can also use the block diagram to configure the clock properties of IFCLK, select different package types, and label the external slave device.

We can see that the naming conventions are consistent with the hardware set-up. The single write waveform (waveform 1) is shown below in Figure 13.


 Figure 13.  Single Write waveform in GPIF Designer

For the single write waveform, data is written to the external FIFO in S0 by making CTL0 a logic LOW (WEN is asserted) and placing data on the bus (Activate Data) for one IFCLK cycle (Wait 1). At 48 MHz, one IFCLK cycle is 20.83 ns. With the IFCLK output inverted, this provides enough set-up and hold time for the data.

S1 is a decision-point state that forces an unconditional branch to the IDLE state, which terminates the waveform (no activity occurs in the IDLE state). A decision point state allows you to pick, at most, two terms to evaluate a logical expression. Based on the results of that evaluation, you can control the next state the waveform goes to. See the FX2 Technical Reference Manual for more information on decision point states. Also in S1, CTL0 is a logic HIGH (WEN is de-asserted), and the data bus is tri-stated (De-activate Data).

Every time a single write waveform is initiated, the GPIF engine will cycle through S0, S1, and then IDLE (S7).

The single read waveform (waveform 0) is very similar to the single write waveform. The single read waveform is shown in Figure 14.

 


Figure 14. Single Read waveform in GPIF Designer

For the single read waveform, CTL1 starts off as a logic LOW in S0 (REN asserted) for one IFCLK cycle. This is to account for a t ENS set-up time for the external FIFO before OE (CTL2) is asserted. S1 then asserts OE, and in S2 the data bus is sampled (Activate Data) and an unconditional branch to the IDLE state is taken to terminate the waveform (no activity occurs in the IDLE state).

Note that the data bus is sampled in S2 when it would be tempting to sample it in S1. At the beginning of S1, the data is not yet available from the external FIFO, therefore the GPIF has to “catch” the data at the beginning of S2. This is why the data bus is sampled in S2 instead of S1.

Every time a single read waveform is initiated, the GPIF engine will cycle through S0, S1, S2, and then IDLE (S7). Notice that waveforms 2 and 3 are unused for the single transaction example, but will be used later for the FIFO transaction example.

 

Single Transaction Firmware

After the single transaction waveforms were implemented in the GPIF Designer, the next step was to integrate the USB portion of the overlying firmware with the GPIF Designer output to perform write and read operations to and from the external FIFO. To do this a firmware frameworks project was copied and the code that performed the external FIFO operations was added to the TD_Poll() function within FX2_extsyncfifo.c (note that periph.c was renamed to something more meaningful here). Endpoint and GPIF register initialization is performed in the TD_Init() function, which is also within FX2_extsyncfifo.c.

When the user opens up the Keil uVision2 project for the FIFO example, the following should be the list of files shown in the Project Window: 

   

    The contents of these files is as follows:

    fw.c
    Firmware frameworks which handles USB requests and calls the task dispatcher TD_Poll(). 

    Ezusb.lib
    Collection of functions that handle suspend, resume, I2C operations, etc. 

    USBJmpTb.OBJ
    Interrupt vector jump table for USB (INT2) and GPIF/Slave FIFO (INT4) interrupt sources. 

    dscr.a51
    Device descriptor tables for the FIFO example which report EP2OUT and EP6IN as the available endpoints for the FX2 device. 

    FX2_to_extsyncFIFO.c
    Main user application code where TD_Poll() and TD_Init() can be found. The user will mainly be modifying this particular file and will not need to touch fw.c. 

    gpif.c
    File that contains the GPIF waveform descriptor tables that implement the Single/FIFO GPIF transaction waveform behavior.
     

    TD_Init( ) 

    The first task at hand was to setup the endpoints appropriately for this example. The following code switches the CPU clock speed to 48MHz (since at power-on default it is 12MHz), and sets up EP2 as an OUT endpoint, 4x buffered of size 512, and EP6 as an IN endpoint, also 4x buffered of size 512. This setup utilizes the maximum allotted 4KB FIFO space. It also sets up the FIFOs for manual mode, word wide operation and goes through a FIFO reset and arming sequence to ensure that they are ready for data operations.

    // set the CPU clock to 48MHz
    CPUCS = ((CPUCS & ~bmCLKSPD) | bmCLKSPD1);
    SYNCDELAY;

    EP2CFG = 0xA0; // EP2OUT, bulk, size 512, 4x buffered
    SYNCDELAY;
    EP4CFG = 0x00; // EP4 not valid
    SYNCDELAY;
    EP6CFG = 0xE0; // EP6IN, bulk, size 512, 4x buffered
    SYNCDELAY;
    EP8CFG = 0x00; // EP8 not valid
    SYNCDELAY;

    EP2FIFOCFG = 0x01; // manual mode, disable PKTEND zero length send, word ops
    SYNCDELAY;
    EP6FIFOCFG = 0x01; // manual mode, disable PKTEND zero length send, word ops
    SYNCDELAY;

    FIFORESET = 0x80; // set NAKALL bit to NAK all transfers from host
    SYNCDELAY;
    FIFORESET = 0x02; // reset EP2 FIFO
    SYNCDELAY;
    FIFORESET = 0x06; // reset EP6 FIFO
    SYNCDELAY;
    FIFORESET = 0x00; // clear NAKALL bit to resume normal operation
    SYNCDELAY;

    // out endpoints do not come up armed
    // since EP2OUT is quad buffered we must write dummy byte counts four times

    EP2BCL = 0x80; // arm EP2OUT by writing byte count w/skip.
    SYNCDELAY;
    EP2BCL = 0x80;
    SYNCDELAY;
    EP2BCL = 0x80;
    SYNCDELAY;
    EP2BCL = 0x80;
    SYNCDELAY;

    GpifInit (); // initialize GPIF registers

    IFCONFIG Register

    TD_Init then calls the function GPIFInit() that resides in gpif.c. GPIFInit() is where the loading of the GPIF waveform descriptor table into on-chip memory takes place and other GPIF registers get setup. An important register, IFCONFIG, also gets setup here to define how the physical interface operates. Table 2 goes through the reasoning behind the setup of the IFCONFIG register for this example.  Note that all these bit assignments were made, automatically by GPIF Designer as a consequence of the Block Diagram configuration.

Bit #

Bit Label

Contents / Description

7

IFCLKSRC

Set to 1 to run the GPIF using the internal clock source

6

3048MHz

Set to 1 to run the internal clock source for the GPIF at 48MHz.

5

IFCLKOE

Set to 1 to turn on the IFCLK output to drive the WCLK and RCLK inputs of the external FIFO.

4

IFCLPOL

Set to 1 to invert the IFCLK output to the external FIFO. This allows enough setup time for the external FIFO.

3

ASYNC

Set to 0 to operate the GPIF at the highest rate (sync mode).

2

GSTATE

Set to 1 to turn on the debug outputs of the state machine. PE[2:0] displays the states the GPIF engine cycles through during each transaction (Note: PE[2:0] are only available on the 100- and 128-pin packages).

1

IFCFG1

Set to 1 to put the FX2 part into GPIF mode (internal master).

0

IFCFG0

Set to 0 to put the FX2 part into GPIF mode (internal master).

Table 2. IFCONFIG register bit settings for FIFO example

    The next thing TD_Init() does is it resets the external FIFO by pulsing PA2. This ensures that the external FIFO is at a ground-zero state before commencing data operations. The following code does the trick:

 

    // reset the external FIFO

    OEA |= 0x04; // turn on PA2 as output pin
    IOA |= 0x04; // pull PA2 high initially
    IOA &= 0xFB; // bring PA2 low
    EZUSB_Delay (1); // keep PA2 low for ~1ms, more than enough time
    IOA |= 0x04; // bring PA2 high

    A vendor command was also setup in the DR_VendorCmnd() function so that the user could reset the external FIFO at any time by performing a vendor request of 0xB2 from the EZ-USB Control Panel. 

    Triggering GPIF Single Transactions

    In order for the data transfers to occur across the physical interface, the CPU needs to trigger the GPIF waveforms by accessing the registers XGPIFSGLDATH, XGPIFSGLDATLX, and XGPIFSGLDATLNOX.

    In order to trigger a GPIF Single Word Write transaction, the user writes to the XGPIFSGLDATAH< and XGPIFSGLDATLX in the following manner:

    XGPIFSGLDATH = <word_value> >> 8;
    XGPIFSGLDATLX = <word_value> // trigger GPIF

    This effectively setups the MSB and LSB of the word value to be transferred, and the sheer act of writing to the XGPIFSGLDATLX register fires off the Single Word Write transaction. To make things a little neater to follow in TD_Poll(), the following function was defined which basically accepts a word value as an input argument and performs the GPIF Single Word Write transaction:

      // reset the external FIFO
      void GPIF_SingleWordWrite( WORD gdata )
      {
          while( !( GPIFTRIG & 0x80 ) ) // poll GPIFTRIG.7 Done bit
          {
            ;
          }
          // using registers in XDATA space
          XGPIFSGLDATH = gdata;
          XGPIFSGLDATLX = gdata >> 8; // trigger GPIF Single Word Write transaction
      }

    This function also checks to see if the GPIF is in the IDLE state (GPIFTRIG.7 is set if GPIF is IDLE) before it launches the transaction. This is something that is necessary before launching any GPIF transaction. Note that the access to the single transaction registers is swapped here because the endpoint buffer is organized as a FIFO. The swapping ensures that the first byte in the endpoint buffer is written out FD[7:0], and the second byte is written out FD[15:8].

    In order to trigger a GPIF Single Word Read transaction, the user performs a dummy read from the XGPIFSGLDATX register. The word value just read will be contained in the registers XGPIFSGLDATH and XGPIFSGLDATLNOX.

    To make things a little neater to follow in TD_Poll(), the following function was defined which basically accepts a word pointer for the destination variable as an input argument and performs the GPIF Single Word Read transaction:

      void GPIF_SingleWordRead( WORD xdata *gdata )
      {
          static BYTE g_data = 0x00; // dummy variable

          while( !( GPIFTRIG & 0x80 ) ) // poll GPIFTRIG.7 Done bit
          {
            ;
          }

          // using register in XDATA space
          g_data = XGPIFSGLDATLX; // dummy read to trigger GPIF Single Word Read transaction

          while( !( GPIFTRIG & 0x80 ) ) // poll GPIFTRIG.7 Done bit
          {
            ;
          }

          // using register(s) in XDATA space, retrieve word just read from external FIFO
          *gdata = ( ( WORD )XGPIFSGLDATLNOX << 8 ) | ( WORD )XGPIFSGLDATH;
      }

    This function first checks to see if the GPIF is IDLE and then performs a dummy read from XGPIFSGLDATLX to fire off the GPIF Single Word Read transaction. Then, another check is performed before accessing the registers that contain the word value.
     

    TD_Poll()

    The TD_Poll() function is where the main application code resides. The firmware here calls the functions GPIF_SingleWordWrite() and GPIF_SingleWordRead to send and receive data from EP2OUT and EP6IN, respectively.

    Code that handles USB OUT transfers

      if(!(EP2468STAT & bmEP2EMPTY) && (EXTFIFONOTFULL))
      {
          // if host sent data to EP2OUT AND external FIFO is not full

          Tcount = (EP2BCH << 8) + EP2BCL; // load transaction count with EP2 byte count
          Tcount /= 2; // divide by 2 for word wide transaction
          Source = (WORD *)(&EP2FIFOBUF);
          for( i = 0x0000; i < Tcount; i++ )
          {
              // transfer data from EP2OUT buffer to external FIFO
              GPIF_SingleWordWrite (*Source);
              Source++;
          }
          EP2BCL = 0x80; // re-arm EP2OUT
      }

    The first thing the OUT handling code does is it checks to see if the host sent data to EP2OUT, and if the external FIFO is not full by accessing the GPIFREADYSTAT register (EXTFIFONOTEMPTY is a macro for GPIFREADYSTAT & bmBIT0).

    If both conditions are met, the word variable Tcount is setup appropriately. Since each GPIF Single Word Write transaction sends an entire word to the external FIFO, the number of transactions is always half the number of bytes actually contained within the endpoint buffer.

    A for loop then calls the GPIF_SingleWordWrite function and indexes through the endpoint buffer values, sending a word out to the external FIFO at a time. The last step then is to re-arm the endpoint buffer so that the next USB data packet can be accepted
    .

    Code that handles USB IN transfers 

      if(in_enable) // if IN transfers are enabled
      {
          if(!(EP2468STAT & bmEP6FULL) && (EXTFIFONOTEMPTY))
          {
          // if EP6IN is not full AND there is data in the external FIFO

          Destination = (WORD *)(&EP6FIFOBUF);
          for( i = 0x0000; i < Tcount; i++ )
          {
              // transfer data from external FIFO to EP6IN buffer
              GPIF_SingleWordRead (Destination);
              Destination++;
          }
          Tcount *= 2; // multiply by 2 to obtain byte count value
          EP6BCH = MSB(Tcount);
          SYNCDELAY;
          EP6BCL = LSB(Tcount); // arm EP6IN to send data to the host
          SYNCDELAY;
          }
      }

    Another vendor command (0xB3) is setup to enable the IN transfers to occur. Otherwise, the code will just sit there and not process the INs. The reason for the in_enable flag is so that the user can test each read and write operation independently. Otherwise, after the OUT handling code, the IN is processed immediately. This is also useful for debugging purposes with the logic analyzer. It allows the user to capture each read/write operation relatively easily.

    If the in_enable flag is set, the code will fall through and check if the EP6IN endpoint buffer is not full, and if the external FIFO is not empty (The CPU can check the status of the RDY signals by accessing the GPIFREADYSTAT register, so EXTFIFONOTEMPTY is a macro for GPIFREADYSTAT & bmBIT1).

    If both conditions are met, a for loop then calls the GPIF_SingleWordRead function and indexes through the endpoint buffer values, receiving a word from the external FIFO at a time. The last step then is to re-arm the endpoint buffer so that the next USB data packet can be accepted. Since each GPIF Single Word Read transaction receives an entire word from the external FIFO, the number of transactions is always half the number of bytes actually contained within the endpoint buffer.

     

Running the example for GPIF Single Transactions
 

Now that the user understands how this FIFO example works, the bulk loop back function can be exercised by performing the steps discussed in this section.

Step 1: Download the firmware using the EZ-USB Control Panel

    a) Unzip the "FX2_extsyncFIFO GPIF Single Transactions.zip" package in the C:\Cypress\Usb\Examples\FX2 directory.

    b) After the user plugs-in the FX2 board, launch the EZ-USB Control Panel and ensure that the selected target is FX2.

    c) Then, press the "Download" button and select the FX2_extsyncFIFO.hex file. The FX2 board renumerates as a Cypress EZ-USB Sample Device and LED0 should come up flashing.

    d) Perform a "Get Pipes" and "Get Dev" to verify one more time that the firmware is up and running. The user should then see the following screen shown below:


 

Step 2: Setup bulk IN transfer and send 512 bytes to the external FIFO

    a) On the same line as the "BulkTrans" button, select Endpoint 6 IN as the "Pipe" and specify a "Length" of 512 bytes. Then click the "BulkTrans" button. This will setup a bulk IN transfer of 512 bytes to read that amount from the external FIFO. Select View -> Pending Ops to see the pending bulk IN transfer.

    b) On the same line as the "FileTrans.." button, select Endpoint 2 OUT as the "Pipe". Press the "FileTrans.." button and select the 512_count.hex file. Click on "Open" and this action will send out 512 bytes out to the external FIFO (the data stream is a ramp).

    c) Even though 512 bytes have been written into the external FIFO the IN transfer is not processed. This is because the in_enable flag in the firmware has not yet been set to TRUE.

 

Step 3: Complete IN transfer to read back 512 bytes from the external FIFO

In order to complete the pending IN transfer and read back 512 bytes from the external FIFO, the in_enable flag must be set to TRUE (remember that this allows the INs to be processed in the TD_Poll routine). To set the flag, on the same line as the "Vend Req" button, enter a value of 0xb3 in the "Req" field. Then click the "Vend Req" button. The user should now see the 512 bytes read back from the external FIFO displayed in the window.

 

The bulk loop back function can also be exercised by running the bulkloop.exe utility supplied with the EZ-USB development kit software. After downloading the firmware, launch the bulkloop.exe utility found in the C:\Cypress\Usb\Bin sub-directory. The user should setup the parameters according to the following screen:

Prior to clicking the "Start" button to commence the bulk loop back transfers, the user should perform the 0xb3 vendor request to set the in_enable flag to TRUE. By clicking the "Start" button, the user should see the "Pass" counter increment as each loop back transfer is exercised. Clicking on the "Stop" button will end the loop back transfers. The data values are also checked by the bulkloop utility on each pass, so the user should see the "Error" count increment if any data value does not match. The application will also stop on any error if the "Stop on Error" checkbox is selected.

Debug Tip:
While running this example and at any time during GPIF development, the user is strongly encouraged to connect a logic analyzer to the relevant signals on the development kit headers. Monitoring the GPIF bus transactions aids debug sessions tremendously, and is essential for anyone seriously interested in writing GPIF firmware. The next topic presents the waveforms the user should see on the logic analyzer as the example is run. An HP1660C Logic Analyzer was used to capture the waveforms.

 

Logic Analyzer Waveforms
 

    Single Write Waveform

       

    The waveform above shows the timing generated by the GPIF engine for the Single Write waveform as defined by the GPIF tool. All the essential signals are presented here, including GSTATE[2:0], which displays the states the GPIF engine cycles through as it performs the Single Write transaction.

    Debug Tip:

    Bringing out the GSTATE signals to the logic analyzer headers allows the user to correlate between the waveforms generated by the GPIF tool, and the actual waveforms generated on the physical interface. This also aids the debugging process because the user can see the immediate effect of changing the waveform behavior in the GPIF tool
    .

    As expected from the GPIF tool output, S0 places the data on the bus (PORTB is FD[7:0] and PORTD is FD[15:8]), and asserts CTL0 (connected to the external FIFO's WEN/ line). This effectively writes the 16-bit data value into the external FIFO. Note here that enough data setup time to the rising edge of IFCLK is provided, since the minimum data setup time for the external FIFO is 4 ns (see CY4265 datasheet). S1 is a decision point state that unconditionally branches to the IDLE state to terminate the transaction. Without the unconditional branch, the GPIF engine would sequentially move through the remaining states until the IDLE state (S7) is reached.

    For every word written out in a bulk OUT transfer, the user should see the GPIF engine cycle through S0, S1, and S7. To capture the waveform, the user should trigger the logic analyzer on the falling edge of CTL0. A sampling rate of 4 ns will give the user the same resolution shown in the waveform above.
     

    Single Read Waveform

     

       

    The waveform above shows the timing generated by the GPIF engine for the Single Read waveform as defined by the GPIF tool. All the essential signals are presented here, including GSTATE[2:0], which displays the states the GPIF engine cycles through as it performs the Single Read transaction.

    As expected from the GPIF tool output, S0 asserts CTL1 (connected to the external FIFO's REN/ line), S1 asserts CTL2 (connected to the external FIFO's OE/ line), and S2 samples the data bus (PORTB is FD[7:0] and PORTD is FD[15:8]). This effectively reads the 16-bit data value into the external FIFO. Note here that enough data setup time to the rising edge of IFCLK is provided, since the minimum data setup time for the external FIFO is 4 ns (see CY4265 datasheet). S2 is a decision point state that unconditionally branches to the IDLE state to terminate the transaction. Without the unconditional branch, the GPIF engine would sequentially move through the remaining states until the IDLE state (S7) is reached.

    For every word read out from the external FIFO in a bulk IN transfer, the user should see the GPIF engine cycle through S0, S1, S2, and S7. To capture the waveform, the user should trigger the logic analyzer on the falling edge of CTL1. A sampling rate of 4 ns will give the user the same resolution shown in the waveform above.